using System;
using System.Collections.Generic;
using System.Text;
using Cysharp.Threading.Tasks;
using LitJson;
using UniGLTF;
using UnityEngine;
using UnityEngine.Networking;

public static class TripoAIScaner
{
    //这里填写tripo后台的apikey
    private const string apiKey = "tsk_AJzP9AMFjaw7OWub0Ux_xDT0TIl5pNOcsYKl7S06LTR";

    private static Dictionary<string, string> headers = new()
    {
        { "Content-Type", "application/json" }, { "Authorization", $"Bearer {apiKey}" }
    };

    private const string TaskURL = "https://api.tripo3d.ai/v2/openapi/task";

    private static Action<int> leftTimeCB;

    private static Action<string> failCB;
    private static Action<string,string> finishCB;
    private static Action<Texture2D> onTex2TexCB;
    private static Action<JsonData> onSetParamCB;
    static string tripoModel_Version;

    private static string qrcodeString;
    /// <summary>
    /// 先用提示词图生图优化图像后，再生成3D模型
    /// </summary>
    /// <param name="focusTex">摄像头拍摄到的贴图</param>
    /// <param name="prompt">图生图步骤的提示词</param>
    /// <param name="picModelVersion">图生图模型可用："flux.1_kontext_pro", "flux.1_dev", "gpt_4o", "gemini_2.5_flash_image_preview"</param>
    /// <param name="tripoModelVersion">tripoAI模型可用："Turbo-v1.0-20250506", "v3.0-20250812", "v2.5-20250123", "v2.0-20240919"</param>
    /// <param name="leftTimeCallBack">剩余时间通知</param>
    /// <param name="finishCallBack">生成模型成功回调,第一个参数是下载链接，第二个参数是二维码</param>
    /// <param name="failCallBack">失败回调</param>
    /// <param name="qrcode">图片上的二维码信息</param>
    public static void Tex2Mesh(Texture2D focusTex, string prompt, string picModelVersion, string tripoModelVersion,
        Action<int> leftTimeCallBack,
        Action<string,string> finishCallBack, Action<string> failCallBack, Action<Texture2D> onTex2TexCallBack,Action<JsonData> onSetParamCallBack,string qrcode)
    {
        tripoModel_Version = tripoModelVersion;
        leftTimeCB = leftTimeCallBack;
        failCB = failCallBack;
        finishCB = finishCallBack;
        onTex2TexCB = onTex2TexCallBack;
        onSetParamCB=onSetParamCallBack;
        qrcodeString=qrcode;

        DoAsync().Forget();

        async UniTask DoAsync()
        {
            var imageData = focusTex.EncodeToPNG();
            var formData = new WWWForm();
            formData.AddBinaryData("file", imageData, "ai.png", "image/png");
            var result1 = await SendPost($"https://api.tripo3d.ai/v2/openapi/upload/sts", formData,
                new Dictionary<string, string> { { "Authorization", $"Bearer {apiKey}" } });
            Debug.Log(result1);
            var json2 = JsonMapper.ToObject(result1);
            var file_token = json2["data"]["image_token"].ToString();

            var jsonReq = new JsonData
            {
                ["type"] = "generate_image",
                ["model_version"] = picModelVersion,
                ["prompt"] = prompt,
                ["file"] = new JsonData { ["type"] = "png", ["file_token"] = file_token }
            };

            var word = jsonReq.ToJson();
            await GeneratePic(word);
        }
    }

    private static string watchingTask;

    private static async UniTask GeneratePic(string word)
    {
        Debug.Log("SendPost " + word);
        var (ret, _) = await SendPost(TaskURL, word, headers);
        Debug.Log(ret);

        if (ret.StartsWith("HTTP"))
        {
            ret = ret[ret.IndexOf("{", StringComparison.Ordinal)..];
        }

        var json = JsonMapper.ToObject(ret);
        if (json["code"].ToString() != "0")
        {
            failCB("文生图请求失败:" + ret);
        }
        else
        {
            watchingTask = json["data"]["task_id"].ToString();
            await LaterPollTask(0);
        }
    }

    static async UniTask LaterPollTask(float time)
    {
        await UniTask.Delay((int)(time * 1000), DelayType.DeltaTime);

        var (_, ret2) = await HttpWebRequestDataAsync($"https://api.tripo3d.ai/v2/openapi/task/{watchingTask}",
            new Dictionary<string, string> { { "Authorization", $"Bearer {apiKey}" } });
        Debug.Log(ret2);
        var json = JsonMapper.ToObject(ret2);
        var status = json["data"]["status"].ToString();
        switch (status)
        {
            case "running" or "queued":
            {
                var delay = 2;
                if (int.TryParse(json["data"]["running_left_time"].ToString(), out var leftTime))
                {
                    //生成时间预估
                    leftTimeCB(leftTime);
                    delay = Mathf.Clamp(leftTime, 2, 10);
                }

                await LaterPollTask(delay);
            }
                break;
            case "success":
            {
                var type = json["data"]["type"].ToString();
                switch (type)
                {
                    case "generate_image":
                        DownloadImage(json).Forget();
                        break;
                    case "text_to_model":
                    case "image_to_model":
                        DownloadMesh(json).Forget();
                        break;
                }

                break;
            }
            case "cancelled":
            {
                failCB("图片生成被取消了!");
            }
                break;
            case "expired":
            case "banned":
            case "unknown":
            case "failed":
            {
                failCB("图片生成失败了："+status);
            }
                break;
        }
    }

    private static async UniTask DownloadImage(JsonData json)
    {
        var imageURL = json["data"]["output"]["generated_image"].ToString();
        if (onTex2TexCB != null)
        {
            var (bytes, _) = await HttpWebRequestDataAsync(imageURL, headers);
            var tex = new Texture2D(0, 0);
            tex.LoadImage(bytes);
            onTex2TexCB(tex);
        }

        Tex2Mesh().Forget();

        async UniTask Tex2Mesh()
        {
            var jsonReq = new JsonData
            {
                ["type"] = "image_to_model", ["model_version"] = tripoModel_Version,
                ["file"] = new JsonData { ["type"] = "png", ["url"] = imageURL }
            };

            await Generate3D(jsonReq);
        }
    }

    private static async UniTask Generate3D(JsonData jsonReq)
    {
        jsonReq["render_image"] = false;
        onSetParamCB(jsonReq);
        //jsonReq["face_limit"] = 3000;
        
        var word = jsonReq.ToJson();
        Debug.Log("SendPost " + word);
        var (ret, _) = await SendPost(TaskURL, word, headers);
        {
            try
            {
                var json = JsonMapper.ToObject(ret);
                if (json["code"].ToString() != "0")
                {
                    failCB("生成模型请求失败:" + ret);
                }
                else
                {
                    watchingTask = json["data"]["task_id"].ToString();
                    await LaterPollTask(0);
                }
            }
            catch (Exception e)
            {
                failCB("生成模型请求失败:" + ret);
            }
        }
    }

    private static async UniTask DownloadMesh(JsonData json)
    {
        string url;
        var output = json["data"]["output"];
        if (output.Keys.Contains("pbr_model"))
        {
            url = output["pbr_model"].ToString();
        }
        else if (output.Keys.Contains("model"))
        {
            url = output["model"].ToString();
        }
        else
        {
            url = output["base_model"].ToString();
        }

        finishCB(url, qrcodeString);
        var (bytes, _) = await HttpWebRequestDataAsync(url, headers);
        
        //如果不需要显示模型，不调用ShowMesh即可
        ShowMesh(bytes);
    }

    private static ImporterContext context;

    private static void ShowMesh(byte[] bytes)
    {
        context?.Destroy(false);
        if (bytes != null)
        {
            context = new ImporterContext();
            context.Load("download.glb", bytes);
            context.ShowMeshes();

            if (context.Meshes.Count > 0)
            {
                var render = context.Root.GetComponentInChildren<MeshRenderer>();
                //由于UniGLTF插件默认用的Shader在HDRP下无法显示，这里用了个简单的自定义shader
                render.sharedMaterial.shader = Shader.Find("Shader Graphs/DemoShader");
            }
        }
    }

    #region Network

    static async UniTask<(byte[], string)> HttpWebRequestDataAsync(string url, Dictionary<string, string> headers)
    {
        try
        {
            var www = UnityWebRequest.Get(url);
            foreach (var header in headers)
            {
                www.SetRequestHeader(header.Key, header.Value);
            }

            await www.SendWebRequest().ToUniTask();
            if (www.result is UnityWebRequest.Result.ConnectionError or UnityWebRequest.Result.ProtocolError)
            {
                Debug.LogError(www.error);
                return (null, www.error);
            }

            return (www.downloadHandler.data, www.downloadHandler.text);
        }
        catch (Exception e)
        {
            Debug.Log($"HttpWebRequestErr url:{url} err:{e.Message}");
        }

        return (null, null);
    }

    static async UniTask<(string, byte[])> SendPost(string _url, string postData,
        Dictionary<string, string> headers = null)
    {
        try
        {
            using var www = new UnityWebRequest(_url, "POST");
            var postBytes = Encoding.UTF8.GetBytes(postData);
            www.uploadHandler = new UploadHandlerRaw(postBytes);
            www.downloadHandler = new DownloadHandlerBuffer();
            www.SetRequestHeader("Content-Type", "application/json");
            if (headers != null)
            {
                foreach (var header in headers)
                {
                    www.SetRequestHeader(header.Key, header.Value);
                }
            }

            await www.SendWebRequest().ToUniTask();

            if (www.result == UnityWebRequest.Result.ConnectionError)
            {
                Debug.Log(www.error);
            }
            else
            {
                //Debug.Log(www.responseCode);
                if (www.responseCode == 200)
                {
                    return (www.downloadHandler.text, www.downloadHandler.data);
                }
            }

            return (null, null);
        }
        catch (Exception e)
        {
            return (e.Message, null);
        }
    }

    static async UniTask<string> SendPost(string _url, WWWForm formData, Dictionary<string, string> headers = null)
    {
        UnityWebRequest www = UnityWebRequest.Post(_url, formData);
        if (headers != null)
        {
            foreach (var header in headers)
            {
                www.SetRequestHeader(header.Key, header.Value);
            }
        }

        await www.SendWebRequest().ToUniTask();
        if (www.result == UnityWebRequest.Result.ConnectionError || www.result == UnityWebRequest.Result.ProtocolError)
        {
            Debug.Log(www.downloadHandler.text);
        }
        else
        {
            return www.downloadHandler.text;
        }

        return null;
    }

    #endregion
}